import {mat4, quat, vec2, vec3, vec4} from 'gl-matrix';
import {renderTriangle} from './renderTriangle';
import {torusData} from './libs';

function printVec(vec: number[] | vec3 | vec4 | vec2) {
  let vecStr = Array.from(vec).
    map(x => x.toFixed(2).
      padStart(5, ' ')).
    join(', ');
  return `(${vecStr})`;
}

function printMat4(mat4: mat4) {
  let nums = Array.from(mat4).
    map(x => x.toFixed(2).
      padStart(5, ' '));
  console.log(`${nums.slice(0, 4).
    join(' ')}
${nums.slice(4, 8).
    join(' ')}
${nums.slice(8, 12).
    join(' ')}
${nums.slice(12, 16).
    join(' ')}`);
}


type v2 = [number, number];
type v3 = [number, number, number];
type v4 = [number, number, number, number];
let armWidth = 0.05;
let armLenR = .3;
let armLenG = .3;
let armLenB = .3;
const shapes: Record<string, { points: v3[], triangles: v3[], colors: v4[], uv?: v2[] }> = {
  axis: {
    points: [
      [0, 0, 0],
      
      // x axis
      [armLenR, -armWidth, 0],
      [armLenR, armWidth, 0],
      [armLenR, 0, -armWidth],
      [armLenR, 0, armWidth],
      
      // y axis
      [-armWidth, armLenG, 0],
      [armWidth, armLenG, 0],
      [0, armLenG, -armWidth],
      [0, armLenG, armWidth],
      
      // z axis
      [-armWidth, 0, armLenB],
      [armWidth, 0, armLenB],
      [0, -armWidth, armLenB],
      [0, armWidth, armLenB],
    
    ],
    triangles: [
      // [0, 1, 2],
      // [0, 3, 4],
      // [0, 5, 6],
      [0, 1, 2],
      [0, 2, 3],
      [0, 3, 4],
      [0, 1, 4],
      [1, 2, 3],
      [2, 3, 4],
      
      [0, 1 + 4, 2 + 4],
      [0, 2 + 4, 3 + 4],
      [0, 3 + 4, 4 + 4],
      [0, 1 + 4, 4 + 4],
      [1 + 4, 2 + 4, 3 + 4],
      [2 + 4, 3 + 4, 4 + 4],
      
      [0, 1 + 8, 2 + 8],
      [0, 2 + 8, 3 + 8],
      [0, 3 + 8, 4 + 8],
      [0, 1 + 8, 4 + 8],
      [1 + 8, 2 + 8, 3 + 8],
      [2 + 8, 3 + 8, 4 + 8],
    
    ],
    colors: [
      [1, 1, 1, 1],
      
      // x axis
      [1, 0, 0, 1],
      [1, 0, 0, 1],
      [1, 0, 0, 1],
      [1, 0, 0, 1],
      
      // y axis
      [0, 1, 0, 1],
      [0, 1, 0, 1],
      [0, 1, 0, 1],
      [0, 1, 0, 1],
  
      // z axis
      [0, 0, 1, 1],
      [0, 0, 1, 1],
      [0, 0, 1, 1],
      [0, 0, 1, 1],
    ],
  },
  qube: {
    points: [
      [0, 0, 0],  // 0
      [1, 0, 0],  // 1
      [1, 1, 0],  // 2
      [0, 1, 0],  // 3
      [0, 0, 1],  // 4
      [1, 0, 1],  // 5
      [1, 1, 1],  // 6
      [0, 1, 1],  // 7
    ],
    triangles: [
      [0, 1, 2],
      [0, 2, 3],
      [0, 1, 5],
      [0, 5, 4],
      [0, 4, 7],
      [0, 7, 3],
      [6, 7, 3],
      [6, 3, 2],
      [6, 2, 1],
      [6, 1, 5],
      [6, 5, 4],
      [6, 4, 7],
    ],
    colors: [
      [0, 0, 0, 1],  // 0
      [1, 0, 0, 1],  // 1
      [1, 1, 0, 1],  // 2
      [0, 1, 0, 1],  // 3
      [0, 0, 1, 1],  // 4
      [1, 0, 1, 1],  // 5
      [1, 1, 1, 1],  // 6
      [0, 1, 1, 1],  // 7
    ],
    uv: [
      [0, 0], // 0
      [1, 0], // 1
      [1, 1], // 2
      [0, 1], // 3
      [1, 0], // 4
      [0, 0], // 5
      [0, 1], // 6
      [1, 1], // 7
    ],
  },
  tour: torusData as any,
};

console.log(shapes.tour);


function doTransformMat4(point: [number, number, number], matMain: mat4) {
  let next = vec3.create();
  vec3.transformMat4(next, point, matMain);
  const [x, y, z] = next as number[];
  return [x, y, z];
}

export async function renderDemo(
  context: CanvasRenderingContext2D,
  scale: v3,
  rotate: v3,
  translate: v3,
  eyePos: v3,
  eyeCenter: v3,
  eyeTop: v3,
  fovY: number = Math.PI / 3 * 2,
  near: number = 1,
  far: number = 100,
  texture: ImageData,
  gloss = 10,
) {
  let {width, height} = context.canvas;
  context.clearRect(0, 0, width, height);
  context.fillStyle = 'skyblue';
  context.fillRect(-0, -0, width, height);
  
  // console.log('eyePos:'.padStart(10, ' '), printVec(eyePos));
  // console.log('eyeCenter:'.padStart(10, ' '), printVec(eyeCenter));
  // console.log('eyeTop:'.padStart(10, ' '), printVec(eyeTop));
  // console.log('fovY:'.padStart(10, ' '), fovY);
  // console.log('near:'.padStart(10, ' '), near);
  // console.log('far:'.padStart(10, ' '), far);
  
  let matMain = mat4.create();
  
  // mat4.identity(matMain);
  
  function matScale(matMain: mat4) {
    // M - 缩放
    console.log('scale:', printVec(scale));
    mat4.scale(matMain, matMain, scale);
    console.log('after scale');
    printMat4(matMain);
  }
  
  function matRotate(matMain: mat4) {
    // M - 旋转
    console.log('rotate:', printVec(rotate));
    let _q = quat.create();
    let _m = mat4.create();
    quat.fromEuler(_q, rotate[0], rotate[1], rotate[2]);
    mat4.fromQuat(_m, _q);
    mat4.mul(matMain, matMain, _m);
    console.log('after rotate');
    printMat4(matMain);
  }
  
  function matMove(matMain: mat4) {
    // M - 移动
    console.log('translate:', printVec(translate));
    mat4.translate(matMain, matMain, translate);
    console.log('after move');
    printMat4(matMain);
  }
  
  
  // 相机空间 V
  function matLookAt(matMain: mat4) {
    console.log('before look at');
    printMat4(matMain);
    let _l = mat4.create();
    mat4.lookAt(
      _l,
      eyePos,
      eyeCenter,
      eyeTop,
    );
    mat4.mul(matMain, matMain, _l);
    console.log('after look at');
    printMat4(matMain);
  }
  
  // 投影 P
  function matProj(matMain: mat4) {
    let _p = mat4.create();
    mat4.perspective(
      _p,
      fovY,
      1, //width / height,
      near,
      far,
    );
    mat4.mul(matMain, matMain, _p);
    console.log('after project');
    printMat4(matMain);
  }
  
  let mute = (cb: any) => {
    let log = console.log;
    console.log = () => {
    };
    cb();
    console.log = log;
  };
  
  function printMainMat() {
    console.log('mat main:');
    printMat4(matMain);
  }
  
  mute(() => {
    matProj(matMain);
    matLookAt(matMain);
    matMove(matMain);
    matRotate(matMain);
    matScale(matMain);
    printMainMat();
  });
  
  
  let shape = shapes['tour'];
  const {
    triangles,
    points,
    colors,
    uv,
  } = shape;
  let zBuffer = new Array(width * height).fill(999999);
  let buffer = new ImageData(width, height);
  
  for (let triangle of triangles) {
    let _points = triangle.map(x => points[x]);
    let _colors: [vec4, vec4, vec4] = triangle.map(x => colors[x]) as [vec4, vec4, vec4];
    
    function computeNormal() {
      let normal = vec3.create();
      let normalView = vec3.create();
      mute(() => {
        let line1 = vec3.create();
        let line2 = vec3.create();
        
        let matN = mat4.create();
        matMove(matN);
        matRotate(matN);
        matScale(matN);
        const [p1, p2, p3] = _points.map(point => {
          let target = vec3.create();
          vec3.transformMat4(target, point, matN);
          return target;
        });
        vec3.subtract(line1, p1, p2);
        vec3.subtract(line2, p2, p3);
        vec3.cross(normal, line1, line2);
        
        // View空间基准的法线
        let matV = mat4.create();
        matLookAt(matV);
        matMove(matV);
        matRotate(matV);
        matScale(matV);
        const [pv1, pv2, pv3] = _points.map(point => {
          let target = vec3.create();
          vec3.transformMat4(target, point, matV);
          return target;
        });
        let lineV1 = vec3.create();
        let lineV2 = vec3.create();
        vec3.subtract(lineV1, pv1, pv2);
        vec3.subtract(lineV2, pv2, pv3);
        vec3.cross(normalView, lineV2, lineV1);
        vec3.normalize(normal, normal);
      });
      return [normal, normalView];
    }
    
    let [normal, normalView] = computeNormal();
    
    let uvs: [v2, v2, v2] | undefined = uv ? triangle.map(x => uv[x]) as [v2, v2, v2] : undefined;
    let xys = _points.map(point => {
      const [x, y, z] = doTransformMat4(point, matMain);
      // console.log(
      //   'point',
      //   printVec(point),
      //   ' => ',
      //   printVec([x, y, z]),
      // );
      return [x, y, z];
    });
    
    let pointOfWorld = _points.map(point => {
      let mat = mat4.create();
      mute(() => {
        matMove(mat);
        matRotate(mat);
        matScale(mat);
      });
      
      return doTransformMat4(
        point,
        mat,
      );
    });
    
    await renderTriangle(
      buffer,
      xys,
      _colors,
      context,
      zBuffer,
      [normal, normalView],
      {
        eyePos,
        uv: uvs,
        texture,
        pointsOfWorld: pointOfWorld,
        gloss,
      },
    );
  }
  
  async function renderPointO() {
    let points: v3[] = [
      [1, 0, 0],
      [0, 1, 0],
      [0, 0, 1],
    ];
    let mat = mat4.create();
    mute(() => {
      matProj(mat);
      matLookAt(mat);
    });
    points = points.map(point => {
      let [x, y, z] = doTransformMat4(point, mat);
      return [x, y, z];
    });
    
    
    await renderTriangle(
      buffer,
      points,
      [
        [1, 0, 0, 1],
        [0, 1, 0, 1],
        [0, 0, 1, 1],
      ],
      context,
      zBuffer,
      [[0, 0, 1], [0, 0, 1]],
      {
        eyePos,
        gloss,
      },
    );
  }
  
  // await renderPointO();
  
  
  context.putImageData(buffer, 0, 0);
}
